import qualified Data.Set as Set
import Data.List (iterate)

type Position = (Int, Int, Int, Int)
type ActiveState = Set.Set Position

enumerate :: [a] -> [(Int, a)]
enumerate = zip [0..]

parseInput :: String -> ActiveState
parseInput str = foldl foldX Set.empty (enumerate $ lines str)
    where
        foldX :: ActiveState -> (Int, String) -> ActiveState
        foldX state (x, tokens) = foldl foldY state (enumerate tokens)
            where 
                foldY :: ActiveState -> (Int, Char) -> ActiveState
                foldY state (y, token) = case token of
                    '#' -> Set.insert (x, y, 0, 0) state
                    '.' -> state

neighbors :: Position -> [Position]
neighbors (x,y,z,w) = map (\(dx,dy,dz,dw) -> (x+dx,y+dy,z+dz,w+dw)) bais
    where
        bais = filter (/=(0,0,0,0)) [ (x,y,z,w) | x <- [-1,0,1], y <- [-1,0,1], z <- [-1,0,1], w <- [-1,0,1] ]

step :: ActiveState -> ActiveState
step state = state''
    where
        activePoses = Set.toList state
        inactivePoses = Set.toList 
                      . Set.fromList 
                      . concat 
                      . map inactiveNeighbors 
                      $ activePoses

        state'  = foldl activeFoldFunc Set.empty activePoses
        state'' = foldl inactiveFoldFunc state' inactivePoses

        isActivePosition :: Position -> Bool
        isActivePosition position = Set.member position state

        inactiveNeighbors :: Position -> [Position]
        inactiveNeighbors = filter (not . isActivePosition) . neighbors

        activeNeighborCount :: Position -> Int
        activeNeighborCount position = length $ filter isActivePosition npos
            where
                npos = neighbors position

        activeFoldFunc :: ActiveState -> Position -> ActiveState
        activeFoldFunc state position = 
            if count == 2 || count == 3
                then Set.insert position state 
                else state
            where
                count = activeNeighborCount position

        inactiveFoldFunc :: ActiveState -> Position -> ActiveState
        inactiveFoldFunc state position = 
            if activeNeighborCount position == 3 
                then Set.insert position state 
                else state

main :: IO ()
main = do
    input <- readFile "input.txt"
    print . length
          . last 
          . take 7
          . iterate step 
          . parseInput $ input